<?php
//
// +----------------------------------------------------------------------+
// | PEAR :: DB_NestedSet_TigraMenu                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license,       |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Authors: Daniel Khan <dk@webcluster.at>                              |
// +----------------------------------------------------------------------+
//
// $Id: TigraMenu.php,v 1.8 2003/10/07 00:11:27 datenpunk Exp $
//

// {{{ DB_NestedSet_TigraMenu:: class

/**
* This class can be used to generate the data to build javascript popup menu
* from a DB_NestedSet node array.
* The Javascript part is done using the free available TigraMenu
* available at http://www.softcomplex.com/products/tigra_menu/.
* Currently version 1.0 is supported.
* Parts of this class where taken ftom the TreemMenu driver by Jason Rust
*
* @author       Daniel Khan <dk@webcluster.at>
* @package      DB_NestedSet
* @version      $Revision: 1.8 $
* @access       public
*/
// }}}
class DB_NestedSet_TigraMenu extends DB_NestedSet_Output {
    // {{{{ properties

    /**
    * @var integer The depth of the current menu.
    * @access private
    */
    var $_levels    = 1;

    /**
    * @var integer The level we started at
    * @access private
    */
    var $_levelOffset = false;


    /**
    * @var array The current menu structure
    * @access private
    */
    var $_structTigraMenu = false;

    /**
    * @var array The longest text for each level
    * @access private
    */
    var $_strlenByLevel = array();

    // }}}
    // {{{ DB_NestedSet_TigraMenu

    /**
    * Constructor
    *
    * @param array $params A hash with parameters needed by the class
    * @see _createFromStructure()
    * @return bool
    **/
    function &DB_NestedSet_TigraMenu($params) {
        $this->_menu_id = $params['menu_id'];
        $this->_structTigraMenu = $this->_createFromStructure($params);
        return true;
    }

    // }}}
    // {{{ _createFromStructure()

    /**
    * Creates the JavaScript array for TigraMenu
    * Initially this method was introduced for the TreeMenu driver by Jason Rust
    *
    * o 'structure' => the result from $nestedSet->getAllNodes(true)
    * o 'textField' => the field in the table that has the text for node
    * o 'linkField' => the field in the table that has the link for the node
    *
    * @access private
    * @return string The TigraMenu JavaScript array
    */
    function &_createFromStructure($params)
    {
        // Basically we go through the array of nodes checking to see
        // if each node has children and if so recursing.  The reason this
        // works is because the data from getAllNodes() is ordered by level
        // so a root node will always be first, and sub children will always
        // be after them.

        static $rootlevel;

        // always start at level 1
        if (!isset($params['currentLevel'])) {
            $params['currentLevel'] = 1;
        }

        if (!isset($rootlevel)) {
            $rootlevel = $params['currentLevel'];
        }

        if (isset($params['tigraMenu'])) {
            $tigraMenu = $tigraMenu.$params['tigraMenu'];
        }

        if(!$this->_levelOffset) {
            $this->_levelOffset = $params['currentLevel'];
        }

        if($this->_levels < ($params['currentLevel']- $this->_levelOffset)) {
            $this->_levels = $params['currentLevel'] - $this->_levelOffset;
        }


        // have to use a while loop here because foreach works on a copy of the array and
        // the child nodes are passed by reference during the recursion so that the parent
        // will know when they have been hit.
        reset($params['structure']);
        while(list($key, $node) = each($params['structure'])) {
            // see if we've already been here before
            if (isset($node['hit']) || $node['level'] < $params['currentLevel']) {
                continue;
            }

            // mark that we've hit this node
            $params['structure'][$key]['hit'] = $node['hit'] = true;

            $tag = array(
            isset($node[$params['textField']]) ? "'".$node[$params['textField']]."'" : 'null',
            isset($node[$params['linkField']]) ? "'".$node[$params['linkField']]."'" : 'null'
            );

            if (!$this->_strlenByLevel[$params['currentLevel'] - $this->_levelOffset] ||
            strlen($node[$params['textField']]) > $this->_strlenByLevel[$params['currentLevel'] - $this->_levelOffset]) {
                $this->_strlenByLevel[$params['currentLevel'] - $this->_levelOffset] = strlen($node[$params['textField']]);
            };

            $tigraMenu = $tigraMenu.$this->_openSubMenu($tag);

            // see if it has children
            if (($node['r'] - 1) != $node['l']) {
                $children = array();
                // harvest all the children
                $tempStructure = $params['structure'];
                foreach ($tempStructure as $childKey => $childNode) {
                    if (!isset($childNode['hit']) &&
                    $node['rootid'] == $childNode['rootid'] &&
                    $node['l'] < $childNode['l'] &&
                    $node['r'] > $childNode['r'] &&
                    $childNode['level'] > $params['currentLevel']) {
                        // important that we assign it by reference here, so that when the child
                        // marks itself 'hit' the parent loops will know
                        $children[] =& $params['structure'][$childKey];
                    }
                }

                $recurseParams = $params;
                $recurseParams['structure'] = $children;
                $recurseParams['currentLevel']++;
                $tigraMenu = $tigraMenu.$this->_createFromStructure($recurseParams);
            }

            $tigraMenu = $tigraMenu.$this->_closeSubMenu();
        }
        return $tigraMenu;
    }

    // }}}
    // {{{ _openMenu()

    /**
    * Returns the string which opens the JavaScript menu
    *
    * @access private
    * @param int $menu_id ID of the menu needed to use more than one menu on a page
    * @return string The JavaScript piece
    */
    function _openMenu($menu_id=1)
    {
        $str = false;
        $str = $str."var MENU_ITEMS".$menu_id." = new Array();\n";
        $str = $str."MENU_ITEMS".$menu_id." = [\n";
        return $str;
    }

    // }}}
    // {{{ _openSubMenu()

    /**
    * Returns the string which opens a submenu within the JavaScript menu
    *
    * @access private
    * @param array $tag Contains the content of the current item (name, link)
    * @return string The JavaScript piece
    */
    function _openSubMenu($tag)
    {
        $rtag = implode(', ', $tag);
        return "\n[".$rtag.',';
    }

    // }}}
    // {{{ _closeMenu()

    /**
    * Closes the JavaScript array
    *
    * @access private
    * @return string The JavaScript piece
    */
    function _closeMenu()
    {

        return '];';
    }

    // }}}
    // {{{ _closeSubMenu()

    /**
    * Closes the JavaScript array of a submenu
    *
    * @access private
    * @return string The JavaScript piece
    */
    function _closeSubMenu()
    {
        return "\n],";
    }

    // }}}
    // {{{ _addStyles()

    /**
    * Creates the JavaScript code which sets the styles for each level
    *
    * @access private
    * @param int $menu_id ID of the menu needed to use more than one menu on a page
    * @param array $rootStyles Array of style attributes for the top items
    * @param array $childStyles Array of style attributes for the sub items
    * @return string The JavaScript piece
    */
    function _addStyles($menu_id, $rootStyles, $childStyles = false)
    {
        if (!$childStyles) {
            $childStyles = $rootStyles;
        }

        $styles = array();
        foreach ($rootStyles as $key => $val) {
            foreach ($val as $skey => $sval) {
                $styles["'$key'"][$skey][] = "'$sval'";
            }
        }

        foreach ($childStyles as $key => $val) {
            foreach ($val as $skey => $sval) {
                for ($i = 1; $i <= $this->_levels; $i++) {
                    $styles["'$key'"][$skey][] = "'$sval'";
                }
            }
        }

        $menustyles = false;
        $menustyles = $menustyles . 'var MENU_STYLES'.$menu_id." = new Array();\n";
        foreach ($styles as $key => $val) {
            $menustyles = $menustyles.'MENU_STYLES'.$menu_id."[$key] = [\n";
            foreach ($val as $skey => $sval) {
                $menustyles = $menustyles . "'$skey', [".implode(', ', $sval)."],\n";
            }
            $menustyles = $menustyles."];\n";
        }

        return $menustyles;
    }

    // }}}
    // {{{ _addGeometry()

    /**
    * Creates the JavaScript code which sets the position and geometry of the menu
    *
    * @access private
    * @param int $menu_id ID of the menu needed to use more than one menu on a page
    * @param array $rootGeometry Array of geometry attributes for the top items
    * @param array $childGeometry  Array of geometry attributes for the sub items
    * @return string The JavaScript piece
    */
    function _addGeometry($menu_id, $rootGeometry, $childGeometry = false)
    {
        if (!$childGeometry) {
            $childGeometry = $rootGeometry;
        }

        $params = array();
        $geometry = array();
        foreach ($rootGeometry as $key => $val) {
            $geometry["'$key'"][] = $val;
            $incr = false;
            if (strpos($val, ',') !== false) {
                list($start, $interval) = explode(',',$val);
                $incr = true;
            }

            $ratio = false;
            if ($key == 'width' && strpos($val, '*') !== false) {
                $ratio = trim(str_replace('*','', $val));
            }
            if ($incr) {
                $val = trim($interval);
                if ($key == 'left' && preg_match('/[+-]/', $interval)) {
                    $val = $params[0]['width'] + trim($val);
                }
            } elseif ($incr) {
                $val = trim($start);
            } elseif ($ratio) {
                $val = $ratio * $this->_strlenByLevel[0];
            }
            $geometry["'$key'"][0] = $val;
            $params[0][$key] = $val;
        }

        foreach($childGeometry as $key => $val) {
            $incr = false;
            if (strpos($val, ',') !== false) {
                list($start, $interval) = explode(',', $val);
                $incr = true;
            }

            $ratio = false;
            if ($key == 'width' && strpos($val, '*') !== false) {
                $ratio = trim(str_replace('*', '', $val));
            }

            for ($i = 1; $i <= $this->_levels; $i++) {
                if ($incr && isset($lastval[$key])) {
                    $val = trim($interval);
                    if($key == 'block_left' && preg_match('/[+-]/', $interval)) {
                        $val = $params[$i - 1]['width'] + trim($val);
                    }
                } elseif($incr) {
                    $val = trim($start);
                } elseif ($ratio) {
                    $val = $ratio * $this->_strlenByLevel[$i];
                    if($val < $params[0]['width']) {
                        $val =  $params[0]['width'];
                    }
                }

                $lastval[$key] = $val;
                $geometry["'$key'"][] = $val;
                $params[$i][$key] = $val;
            }

        }

        $pos = false;
        $pos = $pos . 'var MENU_POS'.$menu_id." = new Array();\n";
        foreach ($geometry as $key => $val) {
            $pos = $pos . 'MENU_POS' . $menu_id . "[$key] = [" . implode(', ', $val) . "];\n";
        }

        return $pos;
    }

    // }}}
    // {{{ printTree()

    /**
    * Print's the current tree using the output driver
    *
    * @access public
    */
    function printTree()
    {
        if (!$options = $this->_getOptions('printTree')) {
            return PEAR::raiseError("TigraMenu::printTree() needs options. See TigraMenu::setOptions()", NESEO_ERROR_NO_OPTIONS, PEAR_ERROR_TRIGGER, E_USER_ERROR);
        }

        echo $this->_openMenu($options['menu_id']) . $this->_structTigraMenu  .$this->_closeMenu();
        echo "\n\n";
        echo $this->_addStyles($options['menu_id'], $options['rootStyles'], $options['childStyles']);
        echo "\n\n";
        echo $this->_addGeometry($options['menu_id'], $options['rootGeometry'], $options['childGeometry']);
    }

    // }}}
}
?>
